package async

import (
	"errors"
	"reflect"
)

var (
	errType = reflect.TypeOf((*error)(nil)).Elem()
)

func invokeReturnNothing(function interface{}, args ...interface{}) error {
	if err := isLegal(function); err != nil {
		return err
	}
	slice := buildArguments(args...)
	returned := reflect.ValueOf(function).Call(slice)
	if len(returned) == 0 {
		// discard possible returned values
		return nil
	}
	panic("can not return values by this call")
}

// invokeReturnSingleValue call a function by reflection
//
// Question: how to build a constraint to restrict worker function (which only return a single value)?
func invokeReturnSingleValue[T1 any, T2 FunctionReturnSingleValue[T1]](function interface{}, args ...interface{}) T1 {
	if err := isLegal(function); err != nil {
		panic(err)
	}
	slice := buildArguments(args...)
	returned := reflect.ValueOf(function).Call(slice)
	if len(returned) == 0 {
		panic("must return a single value")
	}
	if last := returned[len(returned)-1]; isError(last.Type()) {
		if err, _ := last.Interface().(error); err != nil {
			panic(err)
		}
	}
	return returned[0].Interface().(T1)
}

func isError(t reflect.Type) bool {
	return t.Implements(errType)
}

func isLegal(function interface{}) error {
	ftype := reflect.TypeOf(function)
	if ftype == nil {
		return errors.New("can not invoke an untyped nil")
	}
	if ftype.Kind() != reflect.Func {
		return errors.New("can not invoke non-function")
	}
	return nil
}

func buildArguments(args ...interface{}) []reflect.Value {
	slice := make([]reflect.Value, len(args))
	for index, arg := range args {
		slice[index] = reflect.ValueOf(arg)
	}
	return slice
}
